package org.nf.mvc.servlet;

import io.github.classgraph.ClassInfoList;
import org.nf.mvc.annotation.RequestMapping;
import org.nf.mvc.param.BasicTypeHandler;
import org.nf.mvc.param.BeanTypeHandler;
import org.nf.mvc.param.ServletApiTypeHandler;
import org.nf.mvc.util.ScanUtil;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author : LLH
 * @create 2021/5/25
 *
 * 请求总控的Servlet，它将接收所有的http请求，
 * 然后根据请求地址来决定当前请求交给哪一个子控制器
 * 的方法来处理
 */
public class DispatcherServlet extends HttpServlet {

    /**
     * 使用一个map来保存请求方法的信息，key是请求的uri地址，value是MethodDefBean对象
     */
    private static Map<String, MethodDefBean> map = new HashMap<>();
    /**
     * 这个list集合用于存放所有的类型转换器
     */
    private static List<ParamTypeHandler> handlerList =new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 从初始化参数中获取init参数
        String packageName = config.getInitParameter("scanPackageName");

        if(packageName==null){
            throw new ServletException("未指定包路径！");
        }
        // 执行初始化扫描
        initScan(packageName);
        // 初始化参数转换器
        initParamHandler();
    }


    /**
     * 根据包名扫描对应的类，并解析出带有@RequestMapping注解的方法
     * 将其收集到map集合里面
     * @param packageName
     */
    private void initScan(String packageName){
        // 根据包名扫描除Class集合
        ClassInfoList list = ScanUtil.scan(packageName);

        // 循环遍历集合并加载Class对象
        list.forEach(classInfo -> {
            // 加载Class对象，等同于Class.forName()
            Class<?> clazz = classInfo.loadClass();

            // 获取class堆中所有的Method成员
            Method[] methods = clazz.getMethods();

            // 循环遍历方法集合，找出带有注解的Method对象
            for (Method method : methods) {
                if(method.isAnnotationPresent(RequestMapping.class)){
                    // 将带有注解的方法收集起来，放在一个map集合中
                    String uri = method.getAnnotation(RequestMapping.class).value();

                    // 将method、目标class封装成一个Bean对象
                    MethodDefBean bean = new MethodDefBean();
                    // 设置请求处理的method
                    bean.setMethod(method);
                    // 设置方法的参数信息
                    bean.setParameters(method.getParameters());
                    // 设置声明此method的类Class
                    bean.setTargetClass(clazz);

                    //将uri作为key， bean作为value保存到map集合中
                    map.put(uri,bean);
                }
            }
        });
    }


    /**
     *初始化所有的参数转换器
     */
    private void initParamHandler(){
        handlerList.add(new ServletApiTypeHandler());
        handlerList.add(new BasicTypeHandler());
        handlerList.add(new BeanTypeHandler());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取客户端请求的uri地址
        String uri = req.getRequestURI();

        // 将uri放到map集合中进行匹配，匹配成功就取出对应的Method对象来执行回调
        if(map.containsKey(uri)){
            MethodDefBean bean = map.get(uri);
            // 执行请求处理，并返回视图对象
            Object returnView = handleMethod(bean, req, resp);
            // 视图响应
            responseView(returnView,req,resp);
        }else{
            // 其他请求（静态资源请求等等）转发回给容器默认的Servlet来处理
            forwordDefaultServlet(req,resp);
        }
    }


    /**
     * 调用目标对象的方法
     * @param bean
     * @param request
     * @param response
     */
    private Object handleMethod(MethodDefBean bean,HttpServletRequest request,HttpServletResponse response) throws ServletException {
        try {
            // 先根据当前方法找到声明这个方法的类的class对象，然后创建一个实例
            Object instance = bean.getTargetClass().newInstance();
            // 进行参数类型的转换映射，返回一个转换好的Object[]
            Object[] params = handleMethodParams(request, response, bean);
            // 执行方法回调
            Object returnView = bean.getMethod().invoke(instance,params);
            // 返回视图对象
            return returnView;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServletException("请求处理失败",e);
        }

    }

    /**
     * 解析并转换方法所需的参数
     * @param request
     * @param response
     * @param bean
     * @return
     */
    private Object[] handleMethodParams(HttpServletRequest request,HttpServletResponse response,MethodDefBean bean){
        // 先获取方法的所有参数信息
        Parameter[] parameters = bean.getParameters();

        // 定义一个新的Object数组，用于接收转换后的值
        Object[] values = new Object[parameters.length];
        // 循环参数信息
        for(int i=0;i< parameters.length;i++){
            Parameter param = parameters[i];
            // 将这个参数传给参数转换链去处理
            for (ParamTypeHandler hanlder : handlerList) {
                // 设置request和response对象
                hanlder.setRequest(request);
                hanlder.setResponse(response);
                // 进行转换，并返回一个转换好的值
                Object value = hanlder.handle(param);
                if(value!=null){
                    values[i]=value;
                    break;
                }
            }
        }
        return values;
    }


    /**
     * 视图响应
     * @param returnView
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void responseView(Object returnView,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        // 如果returnView为null，表示方法的返回值为void
        if(returnView!=null && returnView instanceof View){
            View view= (View) returnView;
            // 设置request、response对象
            view.setRequest(request);
            view.setResponse(response);
            // 响应视图（多态的调用，运行的是子类的response方法）
            view.response();
        }
    }


    /**
     * 将请求转发给默认的servlet
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void forwordDefaultServlet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        // 获取ServletContext对象 （上下文）
        ServletContext sc = request.getServletContext();
        // 根据默认Servlet配置的<servlet-name>来获取
        sc.getNamedDispatcher("default").forward(request,response);
    }

    /**
     * 销毁servlet前清理map集合
     */
    @Override
    public void destroy() {
        map.clear();
    }
}
